package io.github.bigbird0101.youyishi.spring.bean.load.statistics;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import static io.github.bigbird0101.youyishi.spring.bean.load.statistics.Condition.Action.BEAN_INIT_AFTER;
import static io.github.bigbird0101.youyishi.spring.bean.load.statistics.Condition.Action.BEAN_INIT_BEFORE;
import static io.github.bigbird0101.youyishi.spring.bean.load.statistics.Condition.Action.BEAN_INSTANTIATION_AFTER;
import static io.github.bigbird0101.youyishi.spring.bean.load.statistics.Condition.Action.BEAN_INSTANTIATION_BEFORE;

@RestController
@RequestMapping("/BeanLoadStatistics")
@Slf4j
public class BeanInitBeanFactory implements InstantiationAwareBeanPostProcessor,
        ApplicationListener<ApplicationReadyEvent> {
    static ExecutorService executorService = Executors.newSingleThreadExecutor();
    static Queue<Condition> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
    static Map<String, Statistics> statisticsInstantiationMap = new ConcurrentHashMap<>();
    static Map<String, Statistics> statisticsInitMap = new ConcurrentHashMap<>();
    static Map<String, Statistics> statisticsAllMap = new ConcurrentHashMap<>();
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        try{
            executorService.submit(()->{
                concurrentLinkedQueue.add(Condition.builder()
                        .action(BEAN_INSTANTIATION_BEFORE)
                        .beanName(beanName)
                        .classNameBeforeInstantiation(beanClass.getName())
                        .ts(System.nanoTime())
                        .build());
            });
        } catch (Exception ignored) {

        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        try{
            executorService.submit(()->{
                concurrentLinkedQueue.add(Condition.builder()
                        .action(BEAN_INSTANTIATION_AFTER)
                        .beanName(beanName)
                        .classNameAfterInstantiation(bean.getClass().getName())
                        .ts(System.nanoTime())
                        .build());
            });
        } catch (Exception ignored) {

        }
        return true;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        try{
            executorService.submit(()->{
                concurrentLinkedQueue.add(Condition.builder()
                        .action(BEAN_INIT_BEFORE)
                        .beanName(beanName)
                        .classNameBeforeInit(bean.getClass().getName())
                        .ts(System.nanoTime())
                        .build());
            });
        } catch (Exception ignored) {

        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        try{
            executorService.submit(()->{
                concurrentLinkedQueue.add(Condition.builder()
                        .action(BEAN_INIT_AFTER)
                        .beanName(beanName)
                        .classNameAfterInit(bean.getClass().getName())
                        .ts(System.nanoTime())
                        .build());
            });
        } catch (Exception ignored) {

        }
        return bean;
    }


    @GetMapping("/instantiation")
    public List<Statistics> instantiation() {
        return statisticsInstantiationMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
    }

    @GetMapping("/init")
    public List<Statistics> init() {
        return statisticsInitMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
    }

    @GetMapping("/complete")
    public List<Statistics> complete() {
        return statisticsAllMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
    }

    @GetMapping("/all")
    public Map<String, List<Statistics>> all() {
        List<Statistics> collect = statisticsInstantiationMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
        List<Statistics> init = statisticsInitMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
        List<Statistics> all = statisticsAllMap.values().stream().sorted(Comparator.comparingLong(
                Statistics::getTs).reversed()).collect(Collectors.toList());
        Map<String,List<Statistics>> result=new HashMap<>();
        result.put("instantiation", collect);
        result.put("init", init);
        result.put("complete", all);
        return result;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        log.info("BeanLoadStatistics onApplicationEvent 启动完成");
        new Thread(()->{
            while(!concurrentLinkedQueue.isEmpty()){
                Condition poll = concurrentLinkedQueue.poll();
                if (poll == null) {
                    continue;
                }
                Condition.Action action = poll.getAction();
                switch (action){
                    case BEAN_INSTANTIATION_BEFORE:
                        Statistics statistics=new Statistics();
                        statistics.setClassNameBeforeInstantiation(poll.getClassNameBeforeInstantiation());
                        statistics.setBeanName(poll.getBeanName());
                        statistics.setTs(poll.getTs());
                        statisticsInstantiationMap.put(poll.getBeanName(),statistics);

                        Statistics statistics4=new Statistics();
                        statistics4.setClassNameBeforeInstantiation(poll.getClassNameBeforeInstantiation());
                        statistics4.setBeanName(poll.getBeanName());
                        statistics4.setTs(poll.getTs());
                        statisticsAllMap.put(poll.getBeanName(),statistics4);
                        break;
                    case BEAN_INSTANTIATION_AFTER:
                        Statistics statistics1 = statisticsInstantiationMap.get(poll.getBeanName());
                        statistics1.setClassNameAfterInstantiation(poll.getClassNameAfterInstantiation());
                        statistics1.setTs((poll.getTs() - statistics1.getTs()) / 100_0000);
                        statisticsInstantiationMap.put(poll.getBeanName(),statistics1);
                        break;
                    case BEAN_INIT_BEFORE:
                        Statistics statisticsInit=new Statistics();
                        statisticsInit.setClassNameBeforeInit(poll.getClassNameBeforeInit());
                        statisticsInit.setBeanName(poll.getBeanName());
                        statisticsInit.setTs(poll.getTs());
                        statisticsInitMap.put(poll.getBeanName(),statisticsInit);
                        break;
                    case BEAN_INIT_AFTER:
                        Statistics statistics2 = statisticsInitMap.get(poll.getBeanName());
                        if (null != statistics2) {
                            statistics2.setClassNameAfterInit(poll.getClassNameAfterInit());
                            statistics2.setTs((poll.getTs() - statistics2.getTs()) / 100_0000);
                            statisticsInitMap.put(poll.getBeanName(), statistics2);
                        }

                        Statistics statistics3 = statisticsAllMap.get(poll.getBeanName());
                        if (null != statistics3) {
                            statistics3.setClassNameAfterInit(poll.getClassNameAfterInit());
                            statistics3.setTs((poll.getTs() - statistics3.getTs()) / 100_0000);
                            statisticsAllMap.put(poll.getBeanName(), statistics3);
                        }
                        break;
                    default:
                        throw new UnsupportedOperationException();
                }
            }
        }).start();
    }
}
